# LangChain的函数，工具和代理(五)：Tools & Routing
#  今天我们来学习Langchain中非常有用的工具“tools”,以及用来选择tools的方法“routing”,在之前的几篇博客中我们介绍了如何在langchain中实现openai的函数调用的功能，这里需要强调的是我们之前介绍的langchain的函数调用并非真正意义上的函数调用，而是让llm根据用户信息的上下文来返回被调用函数的参数，真正的函数调用还是需要手动编写函数调用的代码，
#  那么今天我们来介绍一种让langchain实现真正意义上函数调用功能. --- tools

import os

import langchain_core.utils.function_calling as utils
import openai
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser
from pydantic import Field
from pydantic.v1 import BaseModel
# 导入langchain的tool
from langchain.agents import tool
import requests
import datetime
import wikipedia
from langchain.tools.render import format_tool_to_openai_function
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.agent import AgentFinish


openai.api_key = "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"
os.environ['OPENAI_API_KEY'] = "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"


# 一、Tools
# 下面我们来定义一个函数用来查询天气的函数search
# 1、创建一个pydantic类
class OpenMeteoInput(BaseModel):
    latitude: float = Field(..., description="Latitude of the location to fetch weather data for")
    longitude: float = Field(..., description="Longitude of the location to fetch weather data for")


# 2、添加tool装饰器 args_schema是对输入参数做一个类型限制
# @tool(args_schema=OpenMeteoInput)
@tool
def get_current_temperature(latitude: float, longitude: float) -> str:
    """Fetch current temperature for given coordinates."""
    BASE_URL = "https://api.open-meteo.com/v1/forecast"

    # 请求的参数
    params = {
        'latitude': latitude,
        'longitude': longitude,
        'hourly': 'temperature_2m',
        'forecast_days': 1,
    }

    # 发起请求
    response = requests.get(BASE_URL, params=params)
    # 处理请求结果
    if response.status_code == 200:
        results = response.json()
    else:
        raise Exception(f"API Request failed with status code: {response.status_code}")

    current_utc_time = datetime.datetime.now()
    time_list = [datetime.datetime.fromisoformat(time_str.replace('Z', '+00:00')) for time_str in
                 results['hourly']['time']]
    temperature_list = results['hourly']['temperature_2m']

    closest_time_index = min(range(len(time_list)), key=lambda i: abs(time_list[i] - current_utc_time))
    current_temperature = temperature_list[closest_time_index]

    return f'The current temperature is {current_temperature}°C'


# 3、执行函数调用
# response = get_current_temperature.run({"latitude": 13, "longitude": 14})
# print(response)


# 下面我们来定义一个函数用来查询维基百科
@tool
def search_wikipedia(query: str) -> str:
    """Run Wikipedia search and get page summaries."""
    page_titles = wikipedia.search(query)
    summaries = []
    for page_title in page_titles[: 3]:
        try:
            wiki_page = wikipedia.page(title=page_title, auto_suggest=False)
            summaries.append(f"Page: {page_title}\nSummary: {wiki_page.summary}")
        except (
                print("错误")
        ):
            pass
    if not summaries:
        return "No good Wikipedia Search Result was found"
    return "\n\n".join(summaries)


# 执行函数调用
# response = search_wikipedia.run({"query": "熊猫"})
# print(response)

# 二、Routing
# 1、使用langchain的format_tool_to_openai_function方法将函数转换成描述变量比
wikipedia_function = utils.convert_to_openai_function(search_wikipedia)
temperature_function = utils.convert_to_openai_function(get_current_temperature)
functions = [temperature_function, wikipedia_function]
# 2、 接下来我们来定义一个可以查询天气温度和维基百科的chain:
# 2.1 创建函数描述变量
functions = [
    utils.convert_to_openai_function(f) for f in [
        search_wikipedia, get_current_temperature
    ]
]
# 2.2 创建prompt
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are helpful but sassy assistant"),
    ("user", "{input}"),
])
# 2.3 定义llm
model = ChatOpenAI(temperature=0).bind(functions=functions)


# 3、创建一个route函数，我们要借助这个rute函数来让llm实现真正的函数调用：
def route(result):
    if isinstance(result, AgentFinish):
        return result.return_values['output']
    else:
        tools = {
            "search_wikipedia": search_wikipedia,
            "get_current_temperature": get_current_temperature,
        }
        # 执行函数调用
        return tools[result.tool].run(result.tool_input)

# 4、定义chain
chain = prompt | model | OpenAIFunctionsAgentOutputParser() | route
# 调用chain
# response1 = chain.invoke({"input": "上海现在的天气怎么样？"})
response2 = chain.invoke({"input": "langchain 是什么？"})
# response3 = chain.invoke({"input": "你好"})
# print(response1)
print(response2)
# print(response3)


# 三、OpenAPI Specification
# OpenAPI 规范 (OAS) 定义了一个与语言无关的标准 HTTP API 接口，允许人类和计算机发现和理解服务的功能，而无需访问源代码、文档或通过网络流量检查。下面我们再介绍一下langchain中如何将符合OpenAPI规范文本转换成openai的函数描述变量。下面我们有一个符合openAPI规范的函数描述文本，我们要将它转换成openai的函数描述变量：
